{% extends "layout.html" %}
{% block body %}
<script src="../viewer/three.min.js"></script>
<script src="../viewer/TrackballControls.js"></script>
<script src="../viewer/stats.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
			attribute float size;
			attribute vec3 customColor;
			varying vec3 vColor;
			void main() {
				vColor = customColor;
				vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
				//gl_PointSize = max(5.0, size * ( 300.0 / -mvPosition.z ));
			    gl_PointSize = 2.0;
				gl_Position = projectionMatrix * mvPosition;
			}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
			uniform vec3 color;
			uniform sampler2D texture;
			varying vec3 vColor;
			void main() {
				gl_FragColor = vec4( color * vColor, 1.0 );
				//gl_FragColor = gl_FragColor;// * texture2D( texture, gl_PointCoord );
				if ( gl_FragColor.a < ALPHATEST ) discard;
			}
</script>
<h2 id="stat"></h2>
<script type="text/javascript">
    var container, stats;
    var camera, controls, scene, renderer;
    var objects = [];
    init();
    animate();
    function init() {
        window.use_box = true;
        window.frame_rates = [60, 24, 12, 6, 3, 1];
        window.frame_rate_sel = 0;
        window.num_frames = 0;

        container = document.createElement( 'div' );
        document.body.appendChild( container );
        camera = new THREE.PerspectiveCamera( 70, 1, 0.001, 100);
        camera.position.x = 0.5;
        camera.position.y = 0.5;
        camera.position.z = 3;
        camera.up = new THREE.Vector3(0,1,0);
        camera.lookAt(new THREE.Vector3(0.5, 0.5, 0.5));
        camera.updateProjectionMatrix();
        controls = new THREE.TrackballControls(camera);
        controls.rotateSpeed = 2.0;
        controls.zoomSpeed = 2.2;
        controls.panSpeed = 0.8;
        controls.noZoom = false;
        controls.noPan = false;
        controls.staticMoving = true;
        controls.dynamicDampingFactor = 0.3;
        scene = new THREE.Scene();
        scene.background = new THREE.Color( 0xf0f0f0 );
        scene.add( new THREE.AmbientLight( 0x505050 ) );

        var light = new THREE.DirectionalLight( 0xffffff, 1 );
        light.position.set(1, 1, 1).normalize();
        scene.add( light );

        var geometry = new THREE.BoxGeometry(1, 1, 1);
        var material = new THREE.MeshPhongMaterial( {
            color: 0xa0adaf,
            shininess: 10,
            specular: 0x111111,
            side: THREE.BackSide
        } );
        var mesh = new THREE.Mesh( geometry, material );
        mesh.position.x = 0.5;
        mesh.position.y = 0.5;
        mesh.position.z = 0.5;
        mesh.receiveShadow = true;
        if (window.use_box) {
            scene.add( mesh );
        }

        var light = new THREE.SpotLight( 0xffffff, 1.5 );
        light.position.set( 0, 500, 2000 );
        light.castShadow = true; light.shadow = new THREE.LightShadow( new THREE.PerspectiveCamera( 50, 1, 200, 10000 ) ); light.shadow.bias = - 0.00022;
        light.shadow.mapSize.width = 2048;
        light.shadow.mapSize.height = 2048;
        scene.add( light );

        renderer = new THREE.WebGLRenderer({
            antialias: true,
            preserveDrawingBuffer: true
        });
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( 1024, 1024);
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFShadowMap;
        container.appendChild( renderer.domElement );
        var info = document.createElement( 'div' );
        info.style.top = '10px';
        info.style.width = '100%';
        info.style.textAlign = 'center';
        container.appendChild( info );
        stats = new Stats();
        container.appendChild( stats.dom );
        window.addEventListener( 'resize', onWindowResize, false );

        window.frameId = 0;
        window.geometries = {};
        window.recording = false;
        window.playing = false;
        // http://keycode.info/
        document.addEventListener('keydown', function(event) {
            if (event.keyCode == 39 || event.keyCode == 76) {
                refreshFrame(+1);
            }
            else if (event.keyCode == 37 || event.keyCode == 72) {
                window.frameId -= 1;
                refreshFrame(-1);
            } else if (event.keyCode == 80) { // 'p'
                window.playing = !window.playing;
            } else if (event.keyCode == 82) { // 'r'
                window.frame_rate_sel += 1;
                window.frame_rate_sel %= window.frame_rates.length;
            } else if (event.keyCode == 86) { // 'v'
                make_video();
            }
        });
        refreshFrame(0);
        window.last_update = 0;
    }

    function get_frame_rate() {
        return window.frame_rates[window.frame_rate_sel];
    }

    function pad_with_zero(num, size) {
        var s = "000000000000000" + num;
        return s.substr(s.length-size);
    }

    function refreshFrame(inc) {
        var json = {
            path: '{{folder}}/frames/',
            frame_id:window.frameId,
            inc: inc,
            need_geometry: false
        };
        loadJSON(json,
            function(data) {
                reload(window.frameId);
                window.frameId = data.next_frame;
                window.num_frames = Math.max(window.num_frames, window.frameId + 1);
            },
            function(xhr) { console.error(xhr); }
        );
    }

    // Based on
    // https://stackoverflow.com/questions/9838812/how-can-i-open-a-json-file-in-javascript-without-jquery
    // https://stackoverflow.com/questions/4116992/how-to-include-json-data-in-javascript-synchronously-without-parsing
    function loadJSON(json, success, error)
    {
        var sync=true;
        if (sync) {
            var xmlhttp=new XMLHttpRequest();
            xmlhttp.open("POST",'../data',false);
            xmlhttp.setRequestHeader("Content-Type", "application/json");
            xmlhttp.send(JSON.stringify(json));
            if (xmlhttp.status==200)
            {
                success(JSON.parse(xmlhttp.responseText));
            }
            else {
                error(xmlhttp);
                return null;
            }
        } else {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function()
            {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    if (xhr.status === 200) {
                        if (success)
                            success(JSON.parse(xhr.responseText));
                    } else {
                        if (error)
                            error(xhr);
                    }
                }
            };
            xhr.open("POST", path, true);
            xhr.send();
        }
    }

    function postFrame(data, frameId)
    {
        var xmlhttp=new XMLHttpRequest();
        xmlhttp.open("POST",'../upload_frame/' + frameId, false);
        xmlhttp.send(data);
    }

    function clear_frame_buffer() {
        var xmlhttp=new XMLHttpRequest();
        xmlhttp.open("POST",'../clear_frame_buffer', false);
        xmlhttp.send();
    }

    function make_video() {
        window.recording = true;
        clear_frame_buffer();
        window.frameId = 0;
        for (var i = 0; i < window.num_frames; i++) {
            refreshFrame(1);
            render();
            postFrame(renderer.domElement.toDataURL(), window.frameId);
        }
        var xmlhttp=new XMLHttpRequest();
        xmlhttp.open("POST",'../make_video/{{folder}}', false);
        xmlhttp.send();

        // Restore
        window.recording = false;
    }

    function get_geometry(frame_id) {
        if (!(frame_id in window.geometries)) {
            // Load
            var data={};
            var json = {
                path: '{{folder}}/frames/',
                frame_id:frame_id,
                inc: 0,
                need_geometry: true
            };
            loadJSON(json,
                function(data_) {
                    data = data_.data;
                },
                function(xhr) { console.error(xhr); }
            );

            var group = new THREE.Group();
            {
                // Points
                var positions = new Float32Array(data.points.position);
                var colors = new Float32Array(data.points.color);
                var sizes = new Float32Array(data.points.sizes);

                window.num_particles = positions.length / 3;

                var geometry = new THREE.BufferGeometry();
                geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
                geometry.addAttribute( 'customColor', new THREE.BufferAttribute( colors, 3 ) );
                geometry.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );

                var material = new THREE.ShaderMaterial( {
                    uniforms: {
                        color:   { value: new THREE.Color( 0xffffff ) },
                        texture: { value: new THREE.TextureLoader().load( "disc.png" ) }
                    },
                    vertexShader: document.getElementById( 'vertexshader' ).textContent,
                    fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
                    alphaTest: 0.9
                } );
                var particles = new THREE.Points( geometry, material );
                group.add(particles);
            }
            {
                // Triangles
                var positions = new Float32Array(data.triangles.position);
                var colors = new Float32Array(data.triangles.color);
                var triangles = positions.length / 9;
                var geometry = new THREE.BufferGeometry();
                var normals = new Float32Array( triangles * 3 * 3 );

                var color = new THREE.Color();
                var pA = new THREE.Vector3();
                var pB = new THREE.Vector3();
                var pC = new THREE.Vector3();
                var cb = new THREE.Vector3();
                var ab = new THREE.Vector3();
                for ( var i = 0; i < positions.length; i += 9 ) {
                    // positions
                    var ax = positions[ i ];
                    var ay = positions[ i + 1 ];
                    var az = positions[ i + 2 ];
                    var bx = positions[ i + 3 ];
                    var by = positions[ i + 4 ];
                    var bz = positions[ i + 5 ];
                    var cx = positions[ i + 6 ];
                    var cy = positions[ i + 7 ];
                    var cz = positions[ i + 8 ];
                    // flat face normals
                    pA.set( ax, ay, az );
                    pB.set( bx, by, bz );
                    pC.set( cx, cy, cz );
                    cb.subVectors( pC, pB );
                    ab.subVectors( pA, pB );
                    cb.cross( ab );
                    cb.normalize();
                    var nx = cb.x;
                    var ny = cb.y;
                    var nz = cb.z;
                    normals[ i ]     = nx;
                    normals[ i + 1 ] = ny;
                    normals[ i + 2 ] = nz;
                    normals[ i + 3 ] = nx;
                    normals[ i + 4 ] = ny;
                    normals[ i + 5 ] = nz;
                    normals[ i + 6 ] = nx;
                    normals[ i + 7 ] = ny;
                    normals[ i + 8 ] = nz;
                }
                function disposeArray() { this.array = null; }
                geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ).onUpload( disposeArray ) );
                geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ).onUpload( disposeArray ) );
                geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ).onUpload( disposeArray ) );
                geometry.computeBoundingSphere();

                var material = new THREE.MeshPhongMaterial( {
                    color: 0xaaaaaa, specular: 0x000000, shininess: 0,
                    side: THREE.DoubleSide, vertexColors: THREE.VertexColors,
                    polygonOffset: true,
                    polygonOffsetFactor: 1,
                    polygonOffsetUnit: 1
                } );

                var mesh = new THREE.Mesh( geometry, material );

                group.add(mesh);

                var geo = new THREE.WireframeGeometry( mesh.geometry );
                var mat = new THREE.LineBasicMaterial( { color: 0x777777, linewidth: 1});
                var wireframe = new THREE.LineSegments( geo, mat );
                group.add( wireframe );
            }
            window.geometries[frame_id] = group;
        }
        return window.geometries[frame_id]
    }

    function reload(frameId) {
        var particles = get_geometry(frameId);
        if (window.particles) {
            scene.remove(window.particles);
        }
        scene.add(particles);

        window.particles = particles;

        document.getElementById("stat").innerHTML = "Frame: " + window.frameId + " <br> # Particles: " + (window.num_particles) +
            " <br> frame rate:" + get_frame_rate();
    }

    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize( window.innerWidth, window.innerHeight );
    }

    function animate() {
        requestAnimationFrame( animate );
        if (get_frame_rate() == 60) {
            if (window.playing) {
                refreshFrame(1);
            }
        } else {
            if (window.last_update + (1000.0 / get_frame_rate()) < new Date().getTime()) {
                if (window.playing) {
                    refreshFrame(1);
                }
                window.last_update = new Date().getTime();
            }
        }
        render();
        stats.update();
    }
    function render() {
        controls.update();
        renderer.render( scene, camera );
    }
</script>

{% endblock %}